using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using NBear.Common;

namespace NBear.Data
{
    /// <summary>
    /// PageSplit
    /// </summary>
    public abstract class PageSplit : IPageSplit
    {
        #region Const Members

        public const int DEFAULT_PAGESIZE = 10;

        #endregion

        #region Protected Members

        protected static ParameterCache parameterCache = new ParameterCache();
        protected static Dictionary<string, string[]> parameterNameCache = new Dictionary<string, string[]>();
        protected static Dictionary<string, string> _SplitableStatementCache = new Dictionary<string, string>();

        protected string _PageSplitableSelectStatementForFirstPage;
        protected string _PageSplitableSelectStatement;
        protected string _SelectCountStatement;
        protected int _PageSize;
        protected int _RowCount;
        protected Database _DB;
        protected object[] paramValues;

        /// <summary>
        /// Prepares the command.
        /// </summary>
        /// <param name="pageNo">The page no.</param>
        /// <returns></returns>
        protected DbCommand PrepareCommand(int pageNo)
        {
            DbCommand cmd;
            if (pageNo == 1)
            {
                cmd = _DB.GetSqlStringCommand(string.Format(_PageSplitableSelectStatementForFirstPage, _PageSize));
            }
            else
            {
                cmd = _DB.GetSqlStringCommand(string.Format(_PageSplitableSelectStatement, _PageSize, (pageNo - 1) * _PageSize));
            }
            cmd.Connection = _DB.GetConnection();

            string cmdText = cmd.CommandText;
            string[] paramNames = ParseParamNames(cmdText);

            AddParamValues(cmd, paramNames);

            Database.WriteLog(cmd);

            return cmd;
        }

        private void AddParamValues(DbCommand cmd, string[] paramNames)
        {
            lock (parameterCache)
            {
                if (parameterCache.AlreadyCached(cmd))
                {
                    parameterCache.AddParametersFromCache(cmd);

                    if (paramValues != null)
                    {
                        Check.Invariant(paramNames != null && paramNames.Length <= paramValues.Length);

                        for (int i = 0; i < paramNames.Length; i++)
                        {
                            _DB.SetParameterValue(cmd, paramNames[i], paramValues[i]);
                            if (paramNames[i].StartsWith("?"))
                            {
                                cmd.CommandText = cmd.CommandText.Replace(paramNames[i], "?");
                            }
                        }
                    }
                }
                else
                {
                    if (paramValues != null)
                    {
                        Check.Invariant(paramNames != null && paramNames.Length <= paramValues.Length);

                        for (int i = 0; i < paramNames.Length; i++)
                        {
                            _DB.AddInParameter(cmd, paramNames[i], paramValues[i]);
                            if (paramNames[i].StartsWith("?"))
                            {
                                cmd.CommandText = cmd.CommandText.Replace(paramNames[i], "?");
                            }
                        }

                        parameterCache.CacheParameters(cmd);
                    }
                }
            }
        }

        protected string[] ParseParamNames(string cmdText)
        {
            string[] paramNames;
            lock (parameterNameCache)
            {
                if (parameterNameCache.ContainsKey(cmdText))
                {
                    paramNames = parameterNameCache[cmdText];
                }
                else
                {
                    paramNames = _DB.DiscoverParams(cmdText);
                    parameterNameCache.Add(cmdText, paramNames);
                }
            }
            return paramNames;
        }

        protected abstract string ConstructPageSplitableSelectStatement(string sql, string keyColumn);

        protected abstract string ConstructPageSplitableSelectStatementForFirstPage(string sql, string keyColumn);

        protected abstract string ConstructSelectCountStatement(string sql);

        #endregion

        #region Constructors

        protected PageSplit(Database db, string selectStatement, string keyColumn)
        {
            _DB = db;
            _PageSplitableSelectStatementForFirstPage = ConstructPageSplitableSelectStatementForFirstPage(selectStatement, keyColumn);
            _PageSplitableSelectStatement = ConstructPageSplitableSelectStatement(selectStatement, keyColumn);
            _SelectCountStatement = ConstructSelectCountStatement(selectStatement);
            _PageSize = DEFAULT_PAGESIZE;
            _RowCount = -1;
        }

        #endregion

        #region IPageSplit Members

        /// <summary>
        /// Gets or sets the size of the page.
        /// </summary>
        /// <value>The size of the page.</value>
        public int PageSize
        {
            get
            {
                return _PageSize;
            }
            set
            {
                Check.Require(value > 0);

                _PageSize = value;
            }
        }

        /// <summary>
        /// Gets the row count.
        /// </summary>
        /// <returns></returns>
        public int GetRowCount()
        {
            if (_RowCount == -1)
            {
                _RowCount = (int)SimpleDbHelper.SelectScalar(_DB, _SelectCountStatement, paramValues);
            }
            return _RowCount;
        }

        /// <summary>
        /// Gets the page count.
        /// </summary>
        /// <returns></returns>
        public int GetPageCount()
        {
            return (int)Math.Ceiling(1.0 * GetRowCount() / _PageSize);
        }

        /// <summary>
        /// Gets the page.
        /// </summary>
        /// <param name="pageNo">The page no.</param>
        /// <returns>DataSet</returns>
        public System.Data.DataSet GetPage(int pageNo)
        {
            Check.Require(pageNo > 0);
            DbCommand cmd = PrepareCommand(pageNo);
            try
            {
                return _DB.ExecuteDataSet(cmd);
            }
            catch
            {
            }
            finally
            {
                _DB.CloseConnection(cmd);
            }
            return null;
        }

        /// <summary>
        /// Gets the page read only.
        /// </summary>
        /// <param name="pageNo">The page no.</param>
        /// <returns></returns>
        public System.Data.IDataReader GetPageReadOnly(int pageNo)
        {
            if (pageNo <= 0)
            {
                return null;
            }

            DbCommand cmd = PrepareCommand(pageNo);
            IDataReader reader = _DB.ExecuteReader(cmd);
            return reader;
        }

        public Database Db
        {
            get
            {
                return _DB;
            }
            set
            {
                _DB = value;
            }
        }

        #endregion
    }
}
